Skip to content

feat: apple wallet automation#21

Open
gax97 wants to merge 2 commits intomainfrom
feat/ios-shortcuts-integration
Open

feat: apple wallet automation#21
gax97 wants to merge 2 commits intomainfrom
feat/ios-shortcuts-integration

Conversation

@gax97
Copy link
Copy Markdown
Contributor

@gax97 gax97 commented Apr 16, 2026

Summary by CodeRabbit

  • New Features

    • Apple Wallet Automation: Log Apple Pay transactions automatically via iOS Shortcuts with step-by-step setup guide
    • Added Integrations section to Settings (iOS only)
  • Bug Fixes

    • Improved currency handling to gracefully process unsupported or malformed currency codes
  • Chores

    • Version bumped to 1.1.1
    • Updated iOS minimum deployment target to 15.1

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 16, 2026

📝 Walkthrough

Walkthrough

This PR introduces iOS App Intents support for Apple Wallet automation, enabling transaction logging via Shortcuts. It adds new Swift intent classes, React Native bridge modules, a settings UI component, and an Expo configuration plugin. The app version is bumped to 1.1.1, and the iOS deployment target is raised to 15.1.

Changes

Cohort / File(s) Summary
Version Updates
.gitignore, android/app/build.gradle, app.json, ios/Stroberi/Info.plist
Bumped app version from 1.1.0 to 1.1.1 across Android, Expo, and iOS configs. iOS build number incremented from 34 to 48. Added LSMinimumSystemVersion of 12.0 to Info.plist. Gitignore now excludes docs/superpowers.
React Components & Settings UI
app/(tabs)/settings.tsx, components/sheet/ShortcutsSetupSheet.tsx
Added iOS-only "Integrations" section to Settings with "Apple Wallet Automation" item. New ShortcutsSetupSheet component renders bottom modal with instructions, preview images, and native test button that invokes ShortcutsModule.testLogTransaction() via NativeModules.
Component Refactoring
app/(tabs)/analytics.tsx, components/home/BudgetAlertCard.tsx, components/sheet/ErrorSheet.tsx, components/sheet/ImportCSVSheet.tsx
Minor formatting and import consolidation across multiple components. Observable chain reformatted to multi-line for readability. JSX props expanded for clarity.
Database & Utility Updates
database/actions/budgets.ts, database/actions/trips.ts, lib/budgetAlerts.test.ts, lib/format.ts
Simplified query formatting in WatermelonDB calls. Enhanced formatCurrency and formatCurrencyWorklet with try/catch fallback for unsupported currency codes. Test helpers refactored with explicit block syntax.
iOS Build Configuration
ios/Podfile, ios/Podfile.properties.json, ios/Stroberi.xcodeproj/project.pbxproj
Replaced legacy autolinking logic with new config_command-based flow. Deployment target changed from 13.4 to 15.1. Added Intents source/resource files to Xcode project. Removed legacy privacy bundle references. Updated build phase identifiers.
iOS Intents Native Layer
ios/Stroberi/Intents/IntentDatabaseHelper.swift, ios/Stroberi/Intents/IntentSQLiteBridge.h, ios/Stroberi/Intents/IntentSQLiteBridge.m, ios/Stroberi/Intents/LogTransactionIntent.swift, ios/Stroberi/Intents/ShortcutsModule.swift, ios/Stroberi/Intents/ShortcutsModule.m
New App Intents framework with currency normalization, amount parsing, SQLite transaction insertion, and HTTP-based exchange rate fetching. Native React bridge module for async test transaction logging with promise-based callbacks.
iOS Project Housekeeping
ios/Stroberi.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist, ios/Stroberi/Images.xcassets/SplashScreen.imageset/Contents.json, ios/Stroberi/Images.xcassets/SplashScreenBackground.imageset/Contents.json, ios/Stroberi/SplashScreen.storyboard, ios/Stroberi/Stroberi-Bridging-Header.h
Removed splash screen image asset configurations and workspace checks. Updated storyboard for device targeting. Bridging header now imports RCTBridgeModule and IntentSQLiteBridge.h.
Expo Plugin System
plugins/withAppIntents.js, plugins/intents/*
New Expo config plugin that copies intent source files into Xcode project and patches bridging header. Duplicated IntentDatabaseHelper, LogTransactionIntent, and ShortcutsModule in plugins directory for Expo prebuild lifecycle.
Package Management
package.json
Added postinstall script and patch-package/postinstall-postinstall dev dependencies for patch management during dependency installation.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant ShortcutsApp as iOS Shortcuts
    participant ShortcutsSetup as ShortcutsSetupSheet
    participant ReactBridge as ShortcutsModule<br/>(React Native Bridge)
    participant LogIntent as LogTransactionIntent<br/>(App Intent)
    participant Helper as IntentDatabaseHelper
    participant SQLite as SQLite Database
    participant HTTP as Exchange Rate API

    User->>ShortcutsSetup: Opens Settings → Integrations
    ShortcutsSetup->>User: Shows setup instructions & preview
    User->>ShortcutsSetup: Taps "Test It" button
    ShortcutsSetup->>ReactBridge: Calls testLogTransaction()
    ReactBridge->>LogIntent: Trigger App Intent with amount & merchant
    LogIntent->>Helper: Parse amount & read base currency
    Helper->>SQLite: Read defaultCurrency from local_storage
    SQLite-->>Helper: Return base currency (e.g., "USD")
    Helper-->>LogIntent: Parsed amount, currency, base currency
    
    alt Currency Conversion Needed
        LogIntent->>Helper: Fetch exchange rate (from → to base)
        Helper->>HTTP: Request exchange rate (5s timeout)
        HTTP-->>Helper: Return rate or nil
        Helper-->>LogIntent: Exchange rate
    end
    
    LogIntent->>Helper: insertTransaction(id, merchant, amount, ...)
    Helper->>SQLite: INSERT into transactions table
    SQLite-->>Helper: Return success/failure
    Helper-->>LogIntent: success boolean
    LogIntent-->>ReactBridge: Return success/error result
    ReactBridge-->>ShortcutsSetup: Resolve/reject promise
    ShortcutsSetup->>User: Show success/error alert
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 A shortcuts path through Apple's way,
New intents bloom, transactions stay,
With currency dance and SQLite's might,
Our Swift bridges glow in native light,
From tabs to Shortcuts, the flow runs true—
Version 1.1.1 hops on through! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: apple wallet automation' accurately summarizes the main change—adding iOS Shortcuts integration for Apple Wallet automation across multiple components and native modules.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ios-shortcuts-integration

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
package.json (1)

4-4: ⚠️ Potential issue | 🟡 Minor

Version mismatch: package.json is still at 1.1.0 while app.json, android/app/build.gradle, and ios/Info.plist are at 1.1.1.

Per the versioning procedure documented in RELEASE_GUIDE.md (lines 33-40), both app.json -> expo.version and package.json -> version must be bumped together.

Proposed fix
-  "version": "1.1.0",
+  "version": "1.1.1",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 4, Update the package.json "version" field from 1.1.0
to 1.1.1 so it matches app.json (expo.version), android/ios manifests and
follows the RELEASE_GUIDE; ensure the package.json "version" entry in the root
JSON is bumped to 1.1.1 and commit the change together with other versioned
files.
🧹 Nitpick comments (1)
components/sheet/ShortcutsSetupSheet.tsx (1)

28-40: Remove inline comments.

As per coding guidelines, comments should not be written in code for files matching **/*.{js,jsx,ts,tsx}.

♻️ Proposed fix
-// Previews shown inline after the step they illustrate (0-indexed).
-// IMG_3788: the finished automation (trigger + Log Transaction action).
-// IMG_3789: Log Transaction action with Amount / Merchant variables mapped.
 const STEP_PREVIEWS: Record<number, { source: number; caption: string }> = {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/sheet/ShortcutsSetupSheet.tsx` around lines 28 - 40, Remove the
inline comment block above STEP_PREVIEWS (the three comment lines describing
previews and IMG filenames) so the file has no inline comments per project
guidelines; locate the constant STEP_PREVIEWS and delete the leading comment
lines that precede it (the lines referencing previews and IMG_3788/IMG_3789),
leaving only the const declaration and its object literal intact, and scan the
rest of ShortcutsSetupSheet.tsx for any other inline comments to remove
similarly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/sheet/ShortcutsSetupSheet.tsx`:
- Around line 59-76: The Linking.openURL call in handleTestPress is executed
before the isTesting guard, so the URL will open even when a test is already
running; move the Linking.openURL invocation to after the isTesting check (or
better, after a successful test) so it only opens when appropriate.
Specifically, in handleTestPress ensure you first check and return early on
isTesting, then call setIsTesting(true), run
NativeModules.ShortcutsModule.testLogTransaction(), and only upon success call
Linking.openURL(...) and Alert.alert(...) before finally setIsTesting(false).

In `@ios/Stroberi/Intents/IntentDatabaseHelper.swift`:
- Around line 207-250: fetchExchangeRate is blocking the App Intent thread by
using DispatchSemaphore inside fetchExchangeRate() (called from
LogTransactionIntent.perform()); change fetchExchangeRate(from:to:) to be async
-> Double? and update callers (e.g., LogTransactionIntent.perform()) to await
it, replace the semaphore/dataTask pattern with URLSession.shared.data(for:
URLRequest) in an async loop over the two URL strings, keep the same
timeoutInterval on the URLRequest, parse the JSON and return the first valid
rate (or nil) — remove all DispatchSemaphore usage and dataTask closures so the
intent thread is not blocked.

In `@ios/Stroberi/Intents/LogTransactionIntent.swift`:
- Around line 42-52: The current else branch in LogTransactionIntent.swift
assigns exchangeRate = 1.0 and amountInBaseCurrency = expenseAmount when
IntentDatabaseHelper.fetchExchangeRate(...) returns nil; instead, do NOT persist
a fake base amount — remove the 1.0/expenseAmount assignments and either (A) set
exchangeRate and amountInBaseCurrency to a clear sentinel (e.g., nil or
Double.nan) and keep conversionStatus = "missing_rate" so the JS layer can
exclude it from base-currency calculations, or (B) abort/return a failed intent
response from the intent handler so the transaction is not saved; update the
code around IntentDatabaseHelper.fetchExchangeRate, exchangeRate,
amountInBaseCurrency and conversionStatus accordingly.

In `@lib/format.ts`:
- Around line 15-17: Remove the inline comment block that begins with
"Intl.NumberFormat throws on unsupported/malformed currency codes." and the
following two lines in lib/format.ts so the TypeScript source contains no inline
comments per repo rules; leave the code logic unchanged and ensure only the
comment lines are deleted around the Intl.NumberFormat-related note.

In `@plugins/intents/IntentDatabaseHelper.swift`:
- Around line 1-2: The template IntentDatabaseHelper.swift still imports SQLite3
and uses raw sqlite3_* calls; remove that direct SQLite usage and switch to the
IntentSQLiteBridge API (call its exposed methods instead of
sqlite3_open/sqlite3_prepare_v2/sqlite3_step/sqlite3_finalize/sqlite3_close) so
the plugin uses the same bridged implementation as the iOS helper; update the
template functions (e.g., any open/prepare/execute/query helpers) to invoke
IntentSQLiteBridge methods and/or consolidate this file with the fixed helper
used by the app (so plugins/withAppIntents.js copies the bridge-based source),
ensuring no direct SQLite3 import or sqlite3_* symbols remain in
IntentDatabaseHelper.swift.

---

Outside diff comments:
In `@package.json`:
- Line 4: Update the package.json "version" field from 1.1.0 to 1.1.1 so it
matches app.json (expo.version), android/ios manifests and follows the
RELEASE_GUIDE; ensure the package.json "version" entry in the root JSON is
bumped to 1.1.1 and commit the change together with other versioned files.

---

Nitpick comments:
In `@components/sheet/ShortcutsSetupSheet.tsx`:
- Around line 28-40: Remove the inline comment block above STEP_PREVIEWS (the
three comment lines describing previews and IMG filenames) so the file has no
inline comments per project guidelines; locate the constant STEP_PREVIEWS and
delete the leading comment lines that precede it (the lines referencing previews
and IMG_3788/IMG_3789), leaving only the const declaration and its object
literal intact, and scan the rest of ShortcutsSetupSheet.tsx for any other
inline comments to remove similarly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0d1ebe85-3074-4439-bc70-186862a64589

📥 Commits

Reviewing files that changed from the base of the PR and between f0ae929 and dd0854a.

⛔ Files ignored due to path filters (8)
  • assets/images/ios_shortcute/IMG_3788-portrait.png is excluded by !**/*.png
  • assets/images/ios_shortcute/IMG_3788.png is excluded by !**/*.png
  • assets/images/ios_shortcute/IMG_3789-portrait.png is excluded by !**/*.png
  • assets/images/ios_shortcute/IMG_3789.png is excluded by !**/*.png
  • ios/Podfile.lock is excluded by !**/*.lock
  • ios/Stroberi/Images.xcassets/SplashScreen.imageset/image.png is excluded by !**/*.png
  • ios/Stroberi/Images.xcassets/SplashScreenBackground.imageset/image.png is excluded by !**/*.png
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (35)
  • .gitignore
  • android/app/build.gradle
  • app.json
  • app/(tabs)/analytics.tsx
  • app/(tabs)/settings.tsx
  • components/home/BudgetAlertCard.tsx
  • components/sheet/ErrorSheet.tsx
  • components/sheet/ImportCSVSheet.tsx
  • components/sheet/ShortcutsSetupSheet.tsx
  • database/actions/budgets.ts
  • database/actions/trips.ts
  • ios/Podfile
  • ios/Podfile.properties.json
  • ios/Stroberi.xcodeproj/project.pbxproj
  • ios/Stroberi.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  • ios/Stroberi/Images.xcassets/SplashScreen.imageset/Contents.json
  • ios/Stroberi/Images.xcassets/SplashScreenBackground.imageset/Contents.json
  • ios/Stroberi/Info.plist
  • ios/Stroberi/Intents/IntentDatabaseHelper.swift
  • ios/Stroberi/Intents/IntentSQLiteBridge.h
  • ios/Stroberi/Intents/IntentSQLiteBridge.m
  • ios/Stroberi/Intents/LogTransactionIntent.swift
  • ios/Stroberi/Intents/ShortcutsModule.m
  • ios/Stroberi/Intents/ShortcutsModule.swift
  • ios/Stroberi/SplashScreen.storyboard
  • ios/Stroberi/Stroberi-Bridging-Header.h
  • lib/budgetAlerts.test.ts
  • lib/format.ts
  • package.json
  • patches/expo-localization+16.0.1.patch
  • plugins/intents/IntentDatabaseHelper.swift
  • plugins/intents/LogTransactionIntent.swift
  • plugins/intents/ShortcutsModule.m
  • plugins/intents/ShortcutsModule.swift
  • plugins/withAppIntents.js
💤 Files with no reviewable changes (3)
  • ios/Stroberi.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  • ios/Stroberi/Images.xcassets/SplashScreen.imageset/Contents.json
  • ios/Stroberi/Images.xcassets/SplashScreenBackground.imageset/Contents.json

Comment on lines +59 to +76
const handleTestPress = useCallback(async () => {
Linking.openURL('https://www.icloud.com/shortcuts/2d1a19f1410a43bda0fc283c46c84520')
if (isTesting) return;
setIsTesting(true);
try {
const { ShortcutsModule } = NativeModules;
if (!ShortcutsModule) {
Alert.alert('Unavailable', 'Shortcuts module is not available on this device.');
return;
}
await ShortcutsModule.testLogTransaction();
Alert.alert('Success', 'Test transaction created! Check your transaction list.');
} catch {
Alert.alert('Error', 'Failed to create test transaction.');
} finally {
setIsTesting(false);
}
}, [isTesting]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

URL opens before the isTesting guard check.

Linking.openURL is called unconditionally on line 60 before the isTesting early return on line 61. If the user taps "Test It" while already testing, the URL will still open. Consider moving the URL open to after the test completes successfully, or after the guard check.

🐛 Proposed fix - open URL after guard check
   const handleTestPress = useCallback(async () => {
-    Linking.openURL('https://www.icloud.com/shortcuts/2d1a19f1410a43bda0fc283c46c84520')
     if (isTesting) return;
     setIsTesting(true);
     try {
       const { ShortcutsModule } = NativeModules;
       if (!ShortcutsModule) {
         Alert.alert('Unavailable', 'Shortcuts module is not available on this device.');
         return;
       }
       await ShortcutsModule.testLogTransaction();
       Alert.alert('Success', 'Test transaction created! Check your transaction list.');
+      Linking.openURL('https://www.icloud.com/shortcuts/2d1a19f1410a43bda0fc283c46c84520');
     } catch {
       Alert.alert('Error', 'Failed to create test transaction.');
     } finally {
       setIsTesting(false);
     }
   }, [isTesting]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleTestPress = useCallback(async () => {
Linking.openURL('https://www.icloud.com/shortcuts/2d1a19f1410a43bda0fc283c46c84520')
if (isTesting) return;
setIsTesting(true);
try {
const { ShortcutsModule } = NativeModules;
if (!ShortcutsModule) {
Alert.alert('Unavailable', 'Shortcuts module is not available on this device.');
return;
}
await ShortcutsModule.testLogTransaction();
Alert.alert('Success', 'Test transaction created! Check your transaction list.');
} catch {
Alert.alert('Error', 'Failed to create test transaction.');
} finally {
setIsTesting(false);
}
}, [isTesting]);
const handleTestPress = useCallback(async () => {
if (isTesting) return;
setIsTesting(true);
try {
const { ShortcutsModule } = NativeModules;
if (!ShortcutsModule) {
Alert.alert('Unavailable', 'Shortcuts module is not available on this device.');
return;
}
await ShortcutsModule.testLogTransaction();
Alert.alert('Success', 'Test transaction created! Check your transaction list.');
Linking.openURL('https://www.icloud.com/shortcuts/2d1a19f1410a43bda0fc283c46c84520');
} catch {
Alert.alert('Error', 'Failed to create test transaction.');
} finally {
setIsTesting(false);
}
}, [isTesting]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/sheet/ShortcutsSetupSheet.tsx` around lines 59 - 76, The
Linking.openURL call in handleTestPress is executed before the isTesting guard,
so the URL will open even when a test is already running; move the
Linking.openURL invocation to after the isTesting check (or better, after a
successful test) so it only opens when appropriate. Specifically, in
handleTestPress ensure you first check and return early on isTesting, then call
setIsTesting(true), run NativeModules.ShortcutsModule.testLogTransaction(), and
only upon success call Linking.openURL(...) and Alert.alert(...) before finally
setIsTesting(false).

Comment on lines +207 to +250
static func fetchExchangeRate(
from targetCurrency: String,
to baseCurrency: String
) -> Double? {
let target = targetCurrency.lowercased()
let base = baseCurrency.lowercased()

let urls = [
"https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/\(target).json",
"https://currency-api.pages.dev/v1/currencies/\(target).json"
]

for urlString in urls {
guard let url = URL(string: urlString) else { continue }

let semaphore = DispatchSemaphore(value: 0)
var resultRate: Double?

var request = URLRequest(url: url)
request.timeoutInterval = 5

let task = URLSession.shared.dataTask(with: request) { data, response, error in
defer { semaphore.signal() }
guard error == nil,
let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200,
let data = data,
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let currencyData = json[target] as? [String: Any],
let rate = currencyData[base] as? Double,
rate > 0, rate.isFinite else {
return
}
resultRate = rate
}
task.resume()
semaphore.wait()

if let rate = resultRate {
return rate
}
}

return nil
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "IntentDatabaseHelper.swift" -o -name "LogTransactionIntent.swift"

Repository: stroberi-app/stroberi

Length of output: 254


🏁 Script executed:

wc -l ios/Stroberi/Intents/IntentDatabaseHelper.swift

Repository: stroberi-app/stroberi

Length of output: 116


🏁 Script executed:

sed -n '207,250p' ios/Stroberi/Intents/IntentDatabaseHelper.swift

Repository: stroberi-app/stroberi

Length of output: 1662


🏁 Script executed:

cat -n ios/Stroberi/Intents/LogTransactionIntent.swift | head -100

Repository: stroberi-app/stroberi

Length of output: 4034


🏁 Script executed:

rg "fetchExchangeRate" ios/Stroberi/Intents/

Repository: stroberi-app/stroberi

Length of output: 264


Avoid blocking the App Intent thread during rate lookup.

LogTransactionIntent.perform() is async but calls fetchExchangeRate() synchronously, which blocks on a semaphore for each of the two URLs (lines 223 and 243). In slow or failing network conditions, this ties up the intent thread for ~10 seconds and can cause Shortcut execution to timeout. Refactor fetchExchangeRate() to be async and use URLSession.data(for:) instead of DispatchSemaphore.wait().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/Stroberi/Intents/IntentDatabaseHelper.swift` around lines 207 - 250,
fetchExchangeRate is blocking the App Intent thread by using DispatchSemaphore
inside fetchExchangeRate() (called from LogTransactionIntent.perform()); change
fetchExchangeRate(from:to:) to be async -> Double? and update callers (e.g.,
LogTransactionIntent.perform()) to await it, replace the semaphore/dataTask
pattern with URLSession.shared.data(for: URLRequest) in an async loop over the
two URL strings, keep the same timeoutInterval on the URLRequest, parse the JSON
and return the first valid rate (or nil) — remove all DispatchSemaphore usage
and dataTask closures so the intent thread is not blocked.

Comment on lines +42 to +52
} else if let rate = IntentDatabaseHelper.fetchExchangeRate(
from: transactionCurrency, to: baseCurrency
) {
exchangeRate = rate
amountInBaseCurrency = expenseAmount * rate
conversionStatus = "ok"
} else {
exchangeRate = 1.0
amountInBaseCurrency = expenseAmount
conversionStatus = "missing_rate"
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't persist a fake base-currency value when the rate is missing.

When the lookup fails, this still writes amountInBaseCurrency = expenseAmount with exchangeRate = 1.0. For any non-base transaction, that records the wrong base amount (e.g. -10 EUR becomes -10 USD) and can silently skew totals. Please fail the intent here, or store a sentinel that the JS layer explicitly excludes from base-currency calculations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/Stroberi/Intents/LogTransactionIntent.swift` around lines 42 - 52, The
current else branch in LogTransactionIntent.swift assigns exchangeRate = 1.0 and
amountInBaseCurrency = expenseAmount when
IntentDatabaseHelper.fetchExchangeRate(...) returns nil; instead, do NOT persist
a fake base amount — remove the 1.0/expenseAmount assignments and either (A) set
exchangeRate and amountInBaseCurrency to a clear sentinel (e.g., nil or
Double.nan) and keep conversionStatus = "missing_rate" so the JS layer can
exclude it from base-currency calculations, or (B) abort/return a failed intent
response from the intent handler so the transaction is not saved; update the
code around IntentDatabaseHelper.fetchExchangeRate, exchangeRate,
amountInBaseCurrency and conversionStatus accordingly.

Comment thread lib/format.ts
Comment on lines +15 to +17
// Intl.NumberFormat throws on unsupported/malformed currency codes.
// Fall back to a plain representation so a single bad row can't
// crash the screen — the user can still see and delete the entry.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove inline comments from TypeScript source

Please remove the inline comments in this block to match repository rules.

As per coding guidelines, **/*.{js,jsx,ts,tsx}: Don't write comments in code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/format.ts` around lines 15 - 17, Remove the inline comment block that
begins with "Intl.NumberFormat throws on unsupported/malformed currency codes."
and the following two lines in lib/format.ts so the TypeScript source contains
no inline comments per repo rules; leave the code logic unchanged and ensure
only the comment lines are deleted around the Intl.NumberFormat-related note.

Comment on lines +1 to +2
import Foundation
import SQLite3
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Diff plugin vs checked-in iOS helper =="
diff -u \
  plugins/intents/IntentDatabaseHelper.swift \
  ios/Stroberi/Intents/IntentDatabaseHelper.swift || true

echo
echo "== Inspect copy logic in the config plugin =="
rg -n -C3 'IntentDatabaseHelper\.swift|plugins/intents|Intents' plugins/withAppIntents.js

echo
echo "== Inspect the bridge rationale =="
sed -n '1,20p' ios/Stroberi/Intents/IntentSQLiteBridge.m

Repository: stroberi-app/stroberi

Length of output: 7037


Plugin template still uses direct SQLite imports, bypassing the bridge.

The checked-in iOS helper removed the SQLite3 import and moved all database access behind IntentSQLiteBridge to avoid the build-time collision documented in ios/Stroberi/Intents/IntentSQLiteBridge.m (incompatible fts5_api definitions between system and vendored SQLite modules). However, the plugin template at plugins/intents/IntentDatabaseHelper.swift still imports SQLite3 and performs raw sqlite3_* calls. Since plugins/withAppIntents.js copies files from plugins/intents/ into the iOS project during prebuild, the next expo prebuild --clean will overwrite the fixed helper with this stale version and reintroduce the build failure. Update the template to match the bridge-based approach, or consolidate both versions to a shared source.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/intents/IntentDatabaseHelper.swift` around lines 1 - 2, The template
IntentDatabaseHelper.swift still imports SQLite3 and uses raw sqlite3_* calls;
remove that direct SQLite usage and switch to the IntentSQLiteBridge API (call
its exposed methods instead of
sqlite3_open/sqlite3_prepare_v2/sqlite3_step/sqlite3_finalize/sqlite3_close) so
the plugin uses the same bridged implementation as the iOS helper; update the
template functions (e.g., any open/prepare/execute/query helpers) to invoke
IntentSQLiteBridge methods and/or consolidate this file with the fixed helper
used by the app (so plugins/withAppIntents.js copies the bridge-based source),
ensuring no direct SQLite3 import or sqlite3_* symbols remain in
IntentDatabaseHelper.swift.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant